/* Copyright (c) 2006, 2009, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */
 
package com.cburch.logisim.std.io;

import java.awt.FontMetrics;

import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.InstanceData;

class KeyboardData implements InstanceData, Cloneable {
    private Value lastClock;
    private char[] buffer;
    private String str;
    private int bufferLength;
    private int cursorPos;
    private boolean dispValid;
    private int dispStart;
    private int dispEnd;
    
    public KeyboardData(int capacity) {
        lastClock = Value.UNKNOWN;
        buffer = new char[capacity];
        clear();
    }
    
    public Object clone() {
        try {
            KeyboardData ret = (KeyboardData) super.clone();
            ret.buffer = (char[]) this.buffer.clone();
            return ret;
        } catch(CloneNotSupportedException e) {
            return null;
        }
    }
    
    public Value setLastClock(Value newClock) {
        Value ret = lastClock;
        lastClock = newClock;
        return ret;
    }
    
    public boolean isDisplayValid() {
        return dispValid;
    }
    
    public int getDisplayStart() {
        return dispStart;
    }
    
    public int getDisplayEnd() {
        return dispEnd;
    }
    
    public int getCursorPosition() {
        return cursorPos;
    }
    
    public void updateBufferLength(int len) {
        synchronized(this) {
            char[] buf = buffer;
            int oldLen = buf.length;
            if(oldLen != len) {
                char[] newBuf = new char[len];
                System.arraycopy(buf, 0, newBuf, 0, Math.min(len, oldLen));
                if(len < oldLen) {
                    if(bufferLength > len) bufferLength = len;
                    if(cursorPos > len) cursorPos = len;
                }
                buffer = newBuf;
                str = null;
                dispValid = false;
            }
        }
    }
    
    public String toString() {
        String s = str;
        if(s != null) return s;
        StringBuffer build = new StringBuffer();
        char[] buf = buffer;
        int len = bufferLength;
        for(int i = 0; i < len; i++) {
            char c = buf[i];
            build.append(Character.isISOControl(c) ? ' ' : c);
        }
        str = build.toString();
        return str;
    }
    
    public char getChar(int pos) {
        return pos >= 0 && pos < bufferLength ? buffer[pos] : '\0';
    }
    
    public int getNextSpecial(int pos) {
        char[] buf = buffer;
        int len = bufferLength;
        for(int i = pos; i < len; i++) {
            char c = buf[i];
            if(Character.isISOControl(c)) return i;
        }
        return -1;
    }
    
    public void clear() {
        bufferLength = 0;
        cursorPos = 0;
        str = "";
        dispValid = false;
        dispStart = 0;
        dispEnd = 0;
    }
    
    public char dequeue() {
        char[] buf = buffer;
        int len = bufferLength;
        if(len == 0) return '\0';
        char ret = buf[0];
        for(int i = 1; i < len; i++) buf[i - 1] = buf[i];
        bufferLength = len - 1;
        int pos = cursorPos;
        if(pos > 0) cursorPos = pos - 1;
        str = null;
        dispValid = false;
        return ret;
    }
    
    public boolean insert(char value) {
        char[] buf = buffer;
        int len = bufferLength;
        if(len >= buf.length) return false;
        int pos = cursorPos;
        for(int i = len; i > pos; i--) buf[i] = buf[i - 1];
        buf[pos] = value;
        bufferLength = len + 1;
        cursorPos = pos + 1;
        str = null;
        dispValid = false;
        return true;
    }
    
    public boolean delete() {
        char[] buf = buffer;
        int len = bufferLength;
        int pos = cursorPos;
        if(pos >= len) return false;
        for(int i = pos + 1; i < len; i++) buf[i - 1] = buf[i];
        bufferLength = len - 1;
        str = null;
        dispValid = false;
        return true;
    }
    
    public boolean moveCursorBy(int delta) {
        int len = bufferLength;
        int pos = cursorPos;
        int newPos = pos + delta;
        if(newPos < 0 || newPos > len) return false;
        cursorPos = newPos;
        dispValid = false;
        return true;
    }
    
    public boolean setCursor(int value) {
        int len = bufferLength;
        if(value > len) value = len;
        int pos = cursorPos;
        if(pos == value) return false;
        cursorPos = value;
        dispValid = false;
        return true;
    }
    
    public void updateDisplay(FontMetrics fm) {
        if(dispValid) return;
        int pos = cursorPos;
        int i0 = dispStart;
        int i1 = dispEnd;
        String str = toString();
        int len = str.length();
        int max = Keyboard.WIDTH - 8 - 4;
        if(str.equals("") || fm.stringWidth(str) <= max) { 
            i0 = 0;
            i1 = len;
        } else {
            // grow to include end of string if possible
            int w0 = fm.stringWidth(str.charAt(0) + "m");
            int w1 = fm.stringWidth("m");
            int w = i0 == 0 ? fm.stringWidth(str)
                    : w0 + fm.stringWidth(str.substring(i0));
            if(w <= max) i1 = len;
            
            // rearrange start/end so as to include cursor
            if(pos <= i0) {
                if(pos < i0) { i1 += pos - i0; i0 = pos; }
                if(pos == i0 && i0 > 0) { i0--; i1--; }
            }
            if(pos >= i1) {
                if(pos > i1) { i0 += pos - i1; i1 = pos; }
                if(pos == i1 && i1 < len) { i0++; i1++; }
            }
            if(i0 <= 2) i0 = 0;
            
            // resize segment to fit
            if(fits(fm, str, w0, w1, i0, i1, max)) { // maybe should grow
                while(fits(fm, str, w0, w1, i0, i1 + 1, max)) i1++;
                while(fits(fm, str, w0, w1, i0 - 1, i1, max)) i0--;
            } else { // should shrink
                if(pos < (i0 + i1) / 2) {
                    i1--;
                    while(!fits(fm, str, w0, w1, i0, i1, max)) i1--;
                } else {
                    i0++;
                    while(!fits(fm, str, w0, w1, i0, i1, max)) i0++;
                }
                
            }
            if(i0 == 1) i0 = 0;
        }
        dispStart = i0;
        dispEnd = i1;
        dispValid = true;
    }
    
    private boolean fits(FontMetrics fm, String str, int w0, int w1,
            int i0, int i1, int max) {
        if(i0 >= i1) return true;
        int len = str.length();
        if(i0 < 0 || i1 > len) return false;
        int w = fm.stringWidth(str.substring(i0, i1));
        if(i0 > 0) w += w0;
        if(i1 < str.length()) w += w1;
        return w <= max;
    }
}